package fr.imag.ufrima.ctte;

import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.Toolkit;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Vector;
import java.util.List;

import fr.imag.ufrima.ctte.enums.ApplicationTaskTypes;
import fr.imag.ufrima.ctte.enums.InteractionTaskTypes;
import fr.imag.ufrima.ctte.enums.OperatorTypes;
import fr.imag.ufrima.ctte.enums.TaskCategories;
import fr.imag.ufrima.ctte.enums.TaskFrequencies;
import fr.imag.ufrima.ctte.enums.UserTaskTypes;

/**
 * Task 
 * This is the data structure that represent a Task.
 * A task is represented by general properties, 
 * decorative properties and structural properties
 * 
 * @author Bassem DEBBABI
 *
 */
public class Task {
	
	// general properties
	private String identifier;
	private String name;	
	private String category;
	private String type;
	private String frequency;
	private List<String> platforms;
	private String description;
	private String preCondition;
	
	// decorative properties
	private boolean iteratif;
	private boolean optional;	
	
	// structure properties
	private TaskDiagram taskDiagram;
	private Operator leftOperator; 
	private Operator rightOperator; 
	private Task topTask;		
	private Vector<Task> subTasks;	
			
	// gui properties	
	protected Point position;	
	private final static Image AbstractImg = Toolkit.getDefaultToolkit().getImage("images\\abstraction.gif");
	private final static Image AbstractImgBW = Toolkit.getDefaultToolkit().getImage("images\\abstraction_bw.gif");
	
	// configuration enum
	public static final int MOVE_THISTASK_ONLY=0;
	public static final int MOVE_THISTASK_AND_SUBTASKS=1;
	public static final int MOVE_THISTASK_AND_TOPTASKS=2;
	
	/**
	 * Constructor
	 * @param taskDiagram parent task diagram
	 * @param parentTask  parent task
	 * @param identifier  identifier
	 */
	public Task(TaskDiagram taskDiagram,Task parentTask, String identifier) {
		// TODO Task class constructor, to be ometed from here
		this.taskDiagram = taskDiagram;
		this.topTask = parentTask;
		this.identifier = identifier;
		this.name = "name";
		this.position = new Point(0,0);
		this.setCategory("Abstract");
		this.subTasks = new Vector<Task>();
	}
	/**
	 * Constructor 2
	 * @param taskDiagram
	 * @param parentTask
	 * @param identifier
	 * @param position
	 */
	public Task(TaskDiagram taskDiagram,Task parentTask, String identifier, Point position) {
		// TODO Task class constructor
		this.taskDiagram = taskDiagram;
		this.topTask = parentTask;
		this.identifier = identifier;
		this.name = "name";
		this.position = position;
		this.setCategory("Abstract");
		this.subTasks = new Vector<Task>();
	}	
	/**
	 * add sub task
	 * @param task new task to be added as sub task for the current task
	 */
	public void addSubTask(Task task){		
		this.subTasks.add(task);
	}
	/**
	 * add sub task by specifying the brother task
	 * @param brotherTask brother task
	 * @param task        to be added task
	 * @param isLeft      is it an the left of the brother task
	 */
	public void addSubTask(Task brotherTask, Task task, boolean isLeft){			
		if (isLeft == false)
			this.subTasks.add(subTasks.indexOf(brotherTask)+1, task);
		else
			this.subTasks.add(subTasks.indexOf(brotherTask), task);
	}
	public void removeSubTask(Task task){
		if (getSubTasks() != null){
			getSubTasks().remove(task);
		}
	}
	public synchronized void remove(){
		if (this.getLeftOperator() != null)
			this.getLeftOperator().remove();		
		if (this.getRightOperator() != null)
			this.getRightOperator().remove();
		/*
		if (this.getSubTasks() != null){
			Iterator<Task> it = this.getSubTasks().iterator();
			if (it != null){
				while (it.hasNext()) {
					Task task = (Task) it.next();
					if (task != null)
						task.remove();
				}
			}
			this.subTasks = null;
		}	*/	
		if (this.getTopTask() != null){
			this.getTopTask().removeSubTask(this);
		}					
	}
	/**
	 * find a task by giving an identifier as criteria
	 * if the current task don't match the criteria, it look on its sub tasks iteratively
	 * @param identifier task identifier
	 * @return founded task or null otherwise
	 */
	public Task findTask(String identifier){
		if (this.getIdentifier().equals(identifier)){
			return this;
		} else {
			if (this.getSubTasks() != null){
				Iterator<Task> it = getSubTasks().iterator();
				while (it.hasNext()) {
					Task task = (Task) it.next();
					Task wantedTask = task.findTask(identifier); 
					if (wantedTask != null){
						return wantedTask;
					}
				}			
			}
			return null;			
		}
	}
	/**
	 * find a task by giving a position as criteria
	 * @param position point
	 * @param delta    an extra distance 
	 * @return founded task or null otherwise
	 */
	public Task findTask(Point position, int delta){
		int x1 = this.getPosition().x - (getTaskDiagram().getNodeWidth()/2  + delta);
		int y1 = this.getPosition().y - (getTaskDiagram().getNodeHeight()/2 + delta);
		int x2 = this.getPosition().x + (getTaskDiagram().getNodeWidth()/2  + delta);
		int y2 = this.getPosition().y + (getTaskDiagram().getNodeHeight()/2 + delta);
		int x = position.x;
		int y = position.y;
		if (x1 <= x && x <= x2 && 
				y1 <= y && y <= y2) {
				return this;
		} else {
			if (this.getSubTasks() != null){
				Iterator<Task> it = getSubTasks().iterator();
				while (it.hasNext()) {
					Task task = (Task) it.next();
					Task wantedTask = task.findTask(position, delta); 
					if (wantedTask != null){
						return wantedTask;
					}
				}			
			}
			return null;			
		}
	}
	/**
	 * find an operator connected to this task or one of its all sub tasks
	 * @param position point 
	 * @return founded operator or null otherwise
	 */
	public Operator findOperator(Point position){		
		if (rightOperator != null){
			int x1 = this.getPosition().x + (getTaskDiagram().getNodeWidth()/2 );
			int y1 = this.getPosition().y - (getTaskDiagram().getNodeHeight()/2);
			int x2 = this.getRightOperator().getEndTask().getPosition().x - (getTaskDiagram().getNodeWidth()/2);
			int y2 = this.getRightOperator().getEndTask().getPosition().y + (getTaskDiagram().getNodeHeight()/2);
			int x = position.x;
			int y = position.y;
			if (x1 <= x && x <= x2 && 
					y1 <= y && y <= y2) {
					return this.getRightOperator();
			} 
		} 
		if (this.getSubTasks() != null){
			Iterator<Task> it = getSubTasks().iterator();
			while (it.hasNext()) {
				Task task = (Task) it.next();
				Operator wantedOperator = task.findOperator(position);
				//Task wantedTask = task.findTask(position, delta); 
				if (wantedOperator != null){
					return wantedOperator;
				}
			}			
		}
		return null;	
	}
	/**
	 * find a place for an operator
	 * @param position
	 * @return temporary operated between two tasks
	 */
	public Task findPlaceForOperator(Point position){		
		if (this.getSubTasks() != null && this.getSubTasksCount() > 1){
			Iterator<Task> it = getSubTasks().iterator();
			Task task1 = null;
			Task task2 = null;
			int x = position.x;
			int y = position.y;
			while (it.hasNext()) {
				if (task2 != null)
					task1 = task2;
				else 
					task1 = (Task) it.next();
				task2 = (Task) it.next();
				if (task2 != null){
					int x1 = task1.getPosition().x + (getTaskDiagram().getNodeWidth()/2 );
					int y1 = task1.getPosition().y - (getTaskDiagram().getNodeHeight()/2);
					int x2 = task2.getPosition().x - (getTaskDiagram().getNodeWidth()/2);
					int y2 = task2.getPosition().y + (getTaskDiagram().getNodeHeight()/2);
					if (x1 <= x && x <= x2 && 
							y1 <= y && y <= y2) {
							return task1;
					}
				}
				Task wantedTask=null;
				if (task1 != null){
				wantedTask = task1.findPlaceForOperator(position); 
				if (wantedTask != null){
					return wantedTask;
				}}
				if (task2 != null){
				wantedTask = task2.findPlaceForOperator(position); 
				if (wantedTask != null){
					return wantedTask;
				}}
			}		
		}
		return null;		
	}
	/**
	 * update the inter-level distance between this task and its sub tasks
	 * @param interLevelDistance distance
	 */
	public void updateInterLevelDistance(int interLevelDistance){
		if (this.getSubTasks() != null){
			Iterator<Task> it = getSubTasks().iterator();
			while (it.hasNext()) {
				Task task = (Task) it.next();
				task.setPosition(new Point(
						task.getPosition().x,
						this.getPosition().y + interLevelDistance*getTaskDiagram().getNodeHeight()));
				task.updateInterLevelDistance(interLevelDistance);
			}
		}
	}
	/**
	 * getting the gui width (in unit) of the sub tree of the given task
	 * @return width in unit
	 */
	public int getSubTreeWidth(){
		int i = 0;
		if (getSubTasksCount() > 0){
			i = getSubTasksCount() - 1;
		}
		if (getSubTasks() != null){
			Iterator<Task> it = getSubTasks().iterator();
			while (it.hasNext()){
				Task t = (Task)it.next();
				i = i + t.getSubTreeWidth();
			}
		}
		return i;
	}
	/**
	 * get the sub tasks count
	 * @return sub tasks count
	 */
	public int getSubTasksCount(){		
		if (subTasks == null)
			return 0;
		return subTasks.size();
	}
	/**
	 * getting the last sub task of the current task
	 * @return the last task or null if the current task haven't sub tasks 
	 */
	public Task getLastSubTask(){
		if (subTasks != null)
			return subTasks.lastElement();
		else
			return null;
	}
	/**
	 * getting sub tasks at the right side of given sub task
	 * @param afterThisTask given sub task
	 * @return list of tasks or null otherwise
	 */
	public List<Task> getSubTasks(Task afterThisTask){		
		if (subTasks != null && subTasks.contains(afterThisTask)==true && subTasks.lastElement() != afterThisTask){
			return subTasks.subList(subTasks.indexOf(afterThisTask)+1, 
				subTasks.indexOf(subTasks.lastElement())+1);
		} else {
			return null;
		}				
	}
	/**
	 * move a task horizontaly
	 * @param distance distance in unit and not in pixel
	 * @param option
	 */
	public void move(int distance, int option){
		this.setPosition(new Point(
				this.getPosition().x + (distance*this.getTaskDiagram().getNodeWidth()),
				this.getPosition().y));
		if (option == MOVE_THISTASK_AND_SUBTASKS){
			Iterator<Task> it = getSubTasks().iterator();
			while (it.hasNext()){
				((Task) it.next()).move(distance, option);
			}
		} else if (option == Task.MOVE_THISTASK_AND_TOPTASKS){
			if (topTask != null){
				topTask.move(distance, option);
			}
		}
	}
	/**
	 * moving all the tasks (and there sub tasks) in right side of the current task
	 * @param distance distance
	 */
	public void moveRightTasks(int distance){
		List<Task> tasks = this.getRightBrothers();				
		if (tasks != null){
			Iterator<Task> it = tasks.iterator();
			while (it.hasNext()){
				Task t = (Task)it.next();
				t.move(distance, Task.MOVE_THISTASK_AND_SUBTASKS);				
			}			
		}
		if (this.getTopTask() != null){
			this.getTopTask().moveRightTasks(distance);
		}
	}
	/**
	 * moving the task  
	 * @param deltaX 
	 * @param deltaY
	 */
	public void moveTo(int deltaX, int deltaY){
		this.setPosition(new Point(
				this.getPosition().x + deltaX,
				this.getPosition().y + deltaY));
		if (this.getSubTasks() != null){
			Iterator<Task> it = getSubTasks().iterator();
			while (it.hasNext()) {
				Task task = (Task) it.next();
				task.moveTo(deltaX, deltaY);
			}
		}
	}
	/**
	 * getting the task at the right side of the current task
	 * @return list of all brother tasks at the right side
	 */
	public List<Task> getRightBrothers(){		
		if (topTask != null){					
			return topTask.getSubTasks(this);
		} else {
		return null;
		}
	}
	
	
	
	/**
	 * Drawing methods
	 */
	public void draw(Graphics2D g2d, boolean drawSubTasks){		
		if (drawSubTasks == true){
			// 1. draw lines inter levels
			if (this.getSubTasks() != null){
				Iterator<Task> it = getSubTasks().iterator();
				while (it.hasNext()){
					Task t = (Task)it.next();
					g2d.drawLine(
							this.getPosition().x, 
							this.getPosition().y, 
							t.getPosition().x, 
							t.getPosition().y);
					// TODO draw operators					
					if (t.getRightOperator()!=null)
						t.getRightOperator().draw(g2d);
				}
			}		
		}
		// 3. draw task				
		Image img = AbstractImgBW;
		//g2d.drawImage(img, center.x, center.y, null);
		int iconWidth = getTaskDiagram().getIconWidth();
		int iconHeight = getTaskDiagram().getIconHeight();
		g2d.drawImage(img, position.x - iconWidth/2, position.y - iconHeight/2, 
				iconWidth, iconHeight,null);
		// 4. draw titles
		drawTitle(g2d);
		// 6. draw sub tasks
		if (drawSubTasks == true){
			if (this.getSubTasks() != null){
				Iterator<Task> it = getSubTasks().iterator();
				while (it.hasNext()){
					Task t = (Task)it.next();				
					t.draw(g2d, drawSubTasks);
				}
			}		
		}
	}
	public void drawSelected(Graphics2D g2d){
		java.awt.Color c = g2d.getColor();		
		g2d.setColor(java.awt.Color.RED);				
		g2d.drawRoundRect(
				this.getPosition().x - getTaskDiagram().getNodeWidth()/2,
				this.getPosition().y - getTaskDiagram().getNodeHeight()/2, 
				getTaskDiagram().getNodeWidth(), 
				getTaskDiagram().getNodeHeight(), 10, 10);		
		g2d.setColor(c);
		Image img = AbstractImg;
		//g2d.drawImage(img, center.x, center.y, null);
		int iconWidth = getTaskDiagram().getIconWidth();
		int iconHeight = getTaskDiagram().getIconHeight();
		g2d.drawImage(img, position.x - iconWidth/2, position.y - iconHeight/2, 
				iconWidth, iconHeight,null);
	}
	/**
	 * 
	 * @param g2d
	 * @param position
	 */
	public void drawHintedOperator(Graphics2D g2d){
		List<Task> rightBrothers = getRightBrothers();
		if (rightBrothers != null && rightBrothers.size() > 0){
			int x1 = this.getPosition().x + (taskDiagram.getNodeWidth()/2 );
			int y1 = this.getPosition().y - (taskDiagram.getOperatorHeight()/2);
			int x2 = rightBrothers.get(0).getPosition().x - (taskDiagram.getNodeWidth()/2);
			int y2 = rightBrothers.get(0).getPosition().y + (taskDiagram.getOperatorHeight()/2);		
			java.awt.Color c = g2d.getColor();	
			g2d.setColor(java.awt.Color.RED);
			g2d.drawRoundRect(x1, y1, 
					x2 - x1, y2 - y1, 10, 10);		
			g2d.setColor(c);
		}
	}
	
	/**
	 * 
	 * @param g2d
	 * @param position 0=LEFT, 1=RIGHT, 2=BOTTOM
	 */
	public void drawHinted(Graphics2D g2d, Point position){
		
		int iconWidth = getTaskDiagram().getIconWidth();
		int iconHeight = getTaskDiagram().getIconHeight();
		int extraHintDistance = 5;//getTaskDiagram().getExtraHintDistance();
		int x1 = this.getPosition().x - iconWidth/2 - extraHintDistance;
		int y1 = this.getPosition().y - iconHeight/2 - extraHintDistance;
		int x2 = this.getPosition().x + iconWidth/2 + extraHintDistance;
		int y2 = this.getPosition().y + iconHeight/2 + extraHintDistance;
		int hintWidth=iconWidth + 2*extraHintDistance;
		int hintHeight=iconHeight + 2*extraHintDistance;
		int x = this.getPosition().x;
		int y = this.getPosition().y;
		java.awt.Color c = g2d.getColor();		
		getTaskDiagram().setInsertPosition(TaskDiagram.InsertPosition.NONE);
		// .left
		if (this.getTopTask() != null){ // don't show this if it is root task
			g2d.setColor(java.awt.Color.GRAY);
			if ((x1-hintWidth) <= position.x && position.x <= (x1) && 
					(y1) <= position.y && position.y <= (y1+hintHeight)){
				g2d.setColor(java.awt.Color.RED);			
				getTaskDiagram().setInsertPosition(TaskDiagram.InsertPosition.LEFT);
			}
			g2d.drawRoundRect(x1-hintWidth, y1, 
					hintWidth, hintHeight, 10, 10);
		}
		// .reight
		if (this.getTopTask() != null){ // don't show this if it is root task
			g2d.setColor(java.awt.Color.GRAY);
			if ((x1+hintWidth) <= position.x && position.x <= (x1+hintWidth+hintWidth) && 
					(y1) <= position.y && position.y <= (y1+hintHeight)){
				g2d.setColor(java.awt.Color.RED);
				getTaskDiagram().setInsertPosition(TaskDiagram.InsertPosition.RIGHT);			
			}
			g2d.drawRoundRect(x1+hintWidth, y1, 
					hintWidth, hintHeight, 10, 10);
		}
		// .bottom
		g2d.setColor(java.awt.Color.GRAY);
		if ((x1) <= position.x && position.x <= (x1+hintWidth) && 
				(y1+hintHeight) <= position.y && position.y <= (y1+hintHeight+hintHeight)) {
			g2d.setColor(java.awt.Color.RED);
			getTaskDiagram().setInsertPosition(TaskDiagram.InsertPosition.BOTTOM);			
		}
		g2d.drawRoundRect(x1, y1+hintHeight, 
				hintWidth, hintHeight, 10, 10);
		
		g2d.setColor(c);
	}
	protected void drawTitle(Graphics2D g2d){
		String txt = this.getIdentifier();
		int iconWidth = getTaskDiagram().getIconWidth();
		int iconHeight = getTaskDiagram().getIconHeight();
		int betweenTaskAndText= getTaskDiagram().getBetweenTaskAndText();
		if (this.isIteratif()){
			txt += " *";
		}
		if (this.isOptional()){
			String s = "[ ";
			s += txt;
			s += " ]";
			txt = s; 
		}
		g2d.setBackground(java.awt.Color.RED);
		g2d.drawString(txt, position.x - iconWidth/2, 
				position.y + iconHeight/2 + betweenTaskAndText);	
	}
	// deprecated
	public void draw(Graphics2D g2d, Point position, boolean selected) {
		// create the image using the toolkit
		//draw(g2d);
		this.position = position;
		int iconWidth = getTaskDiagram().getIconWidth();
		int iconHeight = getTaskDiagram().getIconHeight();
		Image img = AbstractImg;
		g2d.drawImage(img, position.x - iconWidth/2, position.y - iconHeight/2, 
				iconWidth, iconHeight,null);
		drawTitle(g2d);
		//g2d.drawImage(img, center.x, center.y, null);
	}	
	public String toString(){
		return getIdentifier();
	}
	
	//========== Getters and Setters ================================
	public List<String> getCategories(){
		List<String> categories = new ArrayList<String>();
		categories.add(TaskCategories.ABSTRACT);
		categories.add(TaskCategories.USER);
		categories.add(TaskCategories.APPLICATION);
		categories.add(TaskCategories.INTERACTION);
		return categories;
	}
	public List<String> getTypes(){
		List<String> types = new ArrayList<String>();
		types.add("Not specified");
		if (this.getCategory() != null){
			if (this.getCategory().equals(TaskCategories.ABSTRACT)){
				
			} else if (this.getCategory().equals(TaskCategories.USER)){			
				types.add(UserTaskTypes.COMPARING);
				types.add(UserTaskTypes.PLANNING);
				types.add(UserTaskTypes.PROBLEM_SOLVING);
			} else if (this.getCategory().equals(TaskCategories.APPLICATION)){			
				types.add(ApplicationTaskTypes.COMPARISON);
				types.add(ApplicationTaskTypes.FEEDBACK);
				types.add(ApplicationTaskTypes.GENERATING_ALERTS);
				types.add(ApplicationTaskTypes.GROUPING);
				types.add(ApplicationTaskTypes.LOCATE);
				types.add(ApplicationTaskTypes.OVERVIEW);
				types.add(ApplicationTaskTypes.VISUALISE);
			}else if (this.getCategory().equals(TaskCategories.INTERACTION)){			
				types.add(InteractionTaskTypes.CONTROL);
				types.add(InteractionTaskTypes.EDIT);
				types.add(InteractionTaskTypes.MONITORING);
				types.add(InteractionTaskTypes.RESPONDING_TO_ALERTS);
				types.add(InteractionTaskTypes.SELECTION_SINGLE_CHOICE);
				types.add(InteractionTaskTypes.SELECTION_MULTIPLE_CHOICE);
			}
		}
		return types;
	}
	public List<String> getFrequencies(){
		List<String> frequencies = new ArrayList<String>();
		frequencies.add(TaskFrequencies.NOT_SPECIFIED);
		frequencies.add(TaskFrequencies.LOW);
		frequencies.add(TaskFrequencies.MEDUIM);
		frequencies.add(TaskFrequencies.HIGHT);
		return frequencies;
	}
	public Point getPosition() {
		return position;
	}
	public void setPosition(Point position) {		
		this.position = position;		
	}
	
	public static Image getAbstractImg() {
		return AbstractImg;
	}
	public String getIdentifier() {
		return identifier;
	}
	public void setIdentifier(String identifier) {
		this.identifier = identifier;
		taskDiagram.fireTaskPropertiesChangedEvent(this);
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getCategory() {
		return category;
	}
	public void setCategory(String category) {
		this.category = category;
	}
	public String getType() {
		return type;
	}
	public void setType(String type) {
		this.type = type;
	}
	public String getFrequency() {
		if (frequency == null)
			return "Not Specified";
		return frequency;
	}
	public void setFrequency(String frequency) {
		this.frequency = frequency;
	}
	public List<String> getPlatforms() {
		return platforms;
	}
	public void setPlatforms(List<String> platforms) {
		this.platforms = platforms;
	}
	public String getDescription() {
		return description;
	}
	public void setDescription(String description) {
		this.description = description;
	}
	public String getPreCondition() {
		return preCondition;
	}
	public void setPreCondition(String preCondition) {
		this.preCondition = preCondition;
	}
	public boolean isIteratif() {
		return iteratif;
	}
	public void setIteratif(boolean iteratif) {
		this.iteratif = iteratif;
		taskDiagram.fireTaskPropertiesChangedEvent(this);
	}
	public boolean isOptional() {
		return optional;
	}
	public void setOptional(boolean optional) {
		this.optional = optional;
		taskDiagram.fireTaskPropertiesChangedEvent(this);
	}
	public Task getTopTask() {
		return topTask;
	}
	public void setTopTask(Task topTask) {
		this.topTask = topTask;
	}
	public Vector<Task> getSubTasks() {
		return subTasks;
	}
	public void setSubTasks(Vector<Task> subTasks) {
		this.subTasks = subTasks;
	}
	public TaskDiagram getTaskDiagram() {
		return taskDiagram;
	}
	public void setTaskDiagram(TaskDiagram taskDiagram) {
		this.taskDiagram = taskDiagram;
	}
	public Operator getLeftOperator() {
		return leftOperator;
	}
	public void setLeftOperator(Operator leftOperator) {
		this.leftOperator = leftOperator;
	}
	public Operator getRightOperator() {
		return rightOperator;
	}
	public void setRightOperator(Operator rightOperator) {
		this.rightOperator = rightOperator;
	}
}
